#pragma once

// single object pool with listh.h
// Copyright (c) 2019 - 2020 gavingqf (gavingqf@126.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include <stdlib.h>
#include <cassert>
#include "gnmutex.h"
#include "list.h"
#include <memory>
using namespace KCP;

namespace SObjPoolSpace {
	typedef struct list_head list_head;

	// alloc and free macro.
    #define ObjMalloc ::malloc
    #define ObjFree   ::free

	// function for alloc and free.
	inline void* obj_alloc(size_t size) {
		return ObjMalloc(size);
	}
	inline void obj_free(void *data) {
		ObjFree(data);
	}

	// construct is whether construct when get object.
	template <typename T, bool construct = true, typename locker = CNonMutex>
	class object_pool {
	public:
		typedef T value_type;

	private:
		// value wrap.
		struct value_info {
			// value must in the front of value_info.
			value_type value;

			// reference num.
			int        magic_num;

			// double link node.
			list_head  link;
		};

		// resource info.
		struct resource_info {
			list_head  link;
			value_info *array;
			int        size;
		};

	public:
		explicit object_pool() : m_init_size(0), m_grow_size(0) {
			INIT_LIST_HEAD(&m_list_free);
			INIT_LIST_HEAD(&m_list_all);
		}
		virtual ~object_pool() {
			m_free_locker.Lock();
			INIT_LIST_HEAD(&m_list_free);
			m_free_locker.Unlock();

			// release all resource.
			list_head *pos, *n;
			resource_info *pvalue;
			list_for_each_safe(pos, n, &m_list_all) {
				if ((pvalue = list_entry(pos, resource_info, link))) {
					obj_free(pvalue->array);
					obj_free(pvalue);
				} else {
					assert(false && "pointer is nil");
				}
			}
		}

	public:
		// init.
		bool init(int init_size, int grow_size) {
			m_init_size = init_size;
			m_grow_size = grow_size;
			if (!m_init_size && !m_grow_size) return false;

			if (!m_init_size) m_init_size = m_grow_size;
			if (!m_grow_size) m_grow_size = m_init_size;
			return this->allocate(m_init_size);
		}

		// fetch object.
		// compatible with all construction.
		template <typename ...Args>
		value_type* fetch_obj(Args&& ...args) {
			value_type* obj = _fetch();
			if (!obj) return nullptr;
			new (obj) value_type(std::forward<Args>(args)...);
			return obj;
		}

		// release object.
		bool release_obj(value_type* value) {
			if (!value) return false;

			// transfer to wrap_info.
			value_info *shell = (value_info*)value;
			if (!shell) return false;
			assert(shell && "shell is null");

			assert(shell->magic_num == m_reference && "reference info is error");
			if (shell->magic_num != m_reference) 
				return false;

			// pointer check
			list_head *pos, *n;
			resource_info *pvalue;
			list_for_each_safe(pos, n, &m_list_all) {
				if ((pvalue = list_entry(pos, resource_info, link))) {
					if (shell >= pvalue->array && shell < pvalue->array + pvalue->size)
						goto succ; // find it is valid pointer, then goto succ to put back.
				} else {
					assert(false && "get value pointer error");
				}
			}
			assert(false && "can not find user pointer");
			return false;

		succ:
			// set unreferenced.
			shell->magic_num = m_unreference;

			// de construct as possible.
			if (construct) {
				shell->value.~value_type();
			}

			m_free_locker.Lock();
			list_add_tail(&shell->link, &m_list_free);
			m_free_locker.Unlock();
			return true;
		}

	protected:
		// allocate memory.
		bool allocate(int size) {
			assert(size > 0 && "size <= 0");
			if (size <= 0) return false;

			// new resource and push back
			value_info* array = (value_info*)obj_alloc(sizeof(value_info) * size);
			if (!array) return false;
			for (int i = 0; i < size; i++) {
				list_add_tail(&array[i].link, &m_list_free);
			}

			// push to all list objects
			resource_info *t = (resource_info *)obj_alloc(sizeof(resource_info));
			assert(t && "alloc resource info error");
			if (!t) return false;

			t->array = array;
			t->size  = size;
			list_add_tail(&t->link, &m_list_all);
			return true;
		}

		// fetch pointer.
		value_type* _fetch() {
			// lock it
			m_free_locker.Lock();
			if (list_empty(&m_list_free)) { // realloc it.
				if (!allocate(m_grow_size)) {
					m_free_locker.Unlock();
					return nullptr;
				}
			}

			// get first object.
			value_info *object = list_first_entry(&m_list_free, value_info, link);
			assert(object && "object is null");
			list_del(&object->link);
			m_free_locker.Unlock();

			object->magic_num = m_reference;
			return &object->value;
		}

	protected:
		list_head     m_list_all;                  // all resources
		list_head     m_list_free;                 // allocated resource list
		locker        m_free_locker;               // allocated resource locker
		int           m_init_size;                 // init size
		int           m_grow_size;                 // grow size
		static const int m_reference = 0xA110CAED; // reference num.
		static const int m_unreference= 0xDEA110CA;// unreferenced num.
	};
}